import java.util.*;

import javax.vecmath.Vector3f;

import com.bulletphysics.collision.broadphase.*;
import com.bulletphysics.collision.dispatch.*;
import com.bulletphysics.collision.shapes.*;
import com.bulletphysics.dynamics.DiscreteDynamicsWorld;
import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.dynamics.RigidBodyConstructionInfo;
import com.bulletphysics.dynamics.constraintsolver.*;
import com.bulletphysics.linearmath.*;



public class Physics {
	
	private static final int MAX_PROXIES = 100000;	//maximum number of objects
	private static final long MAX_TTL_OF_BULLET = 3; //maximum time for bullet in seconds
	private static final int COLLISION_FACTOR = 45;	//a little bit bigger that o_width, o_length and o_height in Md2
	private DefaultCollisionConfiguration collisionConfiguration;
	private CollisionDispatcher dispatcher;
	private BroadphaseInterface overlappingPairCache;
	private ConstraintSolver solver;
	private List<CollisionShape> collisionShapes = new ArrayList<CollisionShape>();
	private static DiscreteDynamicsWorld dynamicsWorld;
	private List<RigidBody> dynamicObjects = new ArrayList<RigidBody>(); //all objects that can move (we will use this in multiplayer)
	private List<RigidBody> bullets = new ArrayList<RigidBody>();	//list of all bullets
	private List<ObjectBullet> info_bullets = new ArrayList<ObjectBullet>();	//list of additional informations about bullets
	
	public Physics(){
		initPhysics();
	}
	
	protected void initPhysics(){
		
		// collision configuration contains default setup for memory, collision setup
		collisionConfiguration = new DefaultCollisionConfiguration();

		// use the default collision dispatcher. For parallel processing you can use a different dispatcher (see Extras/BulletMultiThreaded)
		dispatcher = new CollisionDispatcher(collisionConfiguration);

		// the maximum size of the collision world. Make sure objects stay within these boundaries
		Vector3f worldAabbMin = new Vector3f(-10000,-10000,-10000);
		Vector3f worldAabbMax = new Vector3f(10000,10000,10000);
		overlappingPairCache = new AxisSweep3(worldAabbMin,worldAabbMax,MAX_PROXIES);

		// the default constraint solver. For parallel processing you can use a different solver (see Extras/BulletMultiThreaded)
		SequentialImpulseConstraintSolver sol = new SequentialImpulseConstraintSolver();
		solver = sol;
		
		dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
		dynamicsWorld.setGravity(new Vector3f(0f, -100f, 0f));
		dynamicsWorld.getDispatchInfo().allowedCcdPenetration = 0f;
	}
	
	public RigidBody addDynamicObject(Vector3f size, Vector3f position){

		// create a dynamic rigidbodies

		CollisionShape colShape = new BoxShape(size); //all dynamics bodies are represented as box
		collisionShapes.add(colShape);

		// Create Dynamic Objects
		Transform startTransform = new Transform();
		startTransform.setIdentity();
		startTransform.origin.set(position.x, position.y, position.z);
		
		float mass = 100f;
		Vector3f localInertia = new Vector3f(0, 0, 0);
		colShape.calculateLocalInertia(mass, localInertia);

		// using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects
		DefaultMotionState myMotionState = new DefaultMotionState(startTransform);
		RigidBodyConstructionInfo rbInfo = new RigidBodyConstructionInfo(mass, myMotionState, colShape, localInertia);
		RigidBody body = new RigidBody(rbInfo);
		body.setRestitution(0.0f);	//how much energy return on collision
		body.setFriction(1f);	//how much object slide (1 - no sliding)
		body.setDamping(0f, 0f);	//rotation in atmosphere
	
		dynamicsWorld.addRigidBody(body);	//add dynamic body and return it for use
		dynamicObjects.add(body);
		return body;
	}
	
	public void addStaticObject(List <Vector3f> vertices){

		// create a static ConvexHullShape
		CollisionShape shape = new ConvexHullShape(vertices);
		collisionShapes.add(shape);
		
		Transform groundTransform = new Transform();
		groundTransform.setIdentity();

		float mass = 0f;

		Vector3f localInertia = new Vector3f(0, 0, 0);

		// using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects
		DefaultMotionState myMotionState = new DefaultMotionState(groundTransform);
		RigidBodyConstructionInfo rbInfo = new RigidBodyConstructionInfo(mass, myMotionState, shape, localInertia);
		RigidBody body = new RigidBody(rbInfo);

		// add the body to the dynamics world
		dynamicsWorld.addRigidBody(body);	
	}
 
	public RigidBody addBullet(Vector3f position){

		// create a dynamic rigidbodies
		CollisionShape colShape = new BoxShape(new Vector3f(1f,1f,1f)); //all dynamics bodies are represented as box
		collisionShapes.add(colShape);

		// Create Dynamic Objects
		Transform startTransform = new Transform();
		startTransform.setIdentity();
		startTransform.origin.set(position.x,position.y+50,position.z);	//bullet's starting position
		
		float mass = 10f;
		Vector3f localInertia = new Vector3f(0, 0, 0);
		colShape.calculateLocalInertia(mass, localInertia);

		DefaultMotionState myMotionState = new DefaultMotionState(startTransform);
		RigidBodyConstructionInfo rbInfo = new RigidBodyConstructionInfo(mass, myMotionState, colShape, localInertia);
		RigidBody body = new RigidBody(rbInfo);
		body.setRestitution(0.0f);	//how much energy return on collision
		body.setFriction(1f);	//how much object slide (1 - no sliding)
		body.setDamping(0f, 0f);	//rotation in atmosphere
		
		body.applyCentralImpulse(SSG.getViewDirections());	//apply force to bullet in direction that we are looking

		body.applyCentralImpulse(SSG.getViewDirections());
		Vector3f linVel = new Vector3f(0f,1f,0f);
		linVel.normalize();
		linVel.scale(500);
		
		
		dynamicsWorld.addRigidBody(body);	//add dynamic body and return it for use
		bullets.add(body);			//add bullet to list
		info_bullets.add(new ObjectBullet(System.nanoTime()));
		
		return body;
	}
	
	public static  DiscreteDynamicsWorld getDynamicsWorld(){
		return dynamicsWorld;
	}
	
	public boolean checkJump(){
		//we can jump only if we are not in air (if we have y velocity around 0)
		Vector3f velocity = new Vector3f();
		dynamicObjects.get(0).getLinearVelocity(velocity);
		if(velocity.y > 0.9 || velocity.y < -0.9) //lower this number if you can jump while in air
			return false;
		else
			return true;
	}

	public boolean isBullet(CollisionObject o){
		for(int i = 0; i < bullets.size(); i++){
			if(o.equals(bullets.get(i)))
				return true;
		}
		return false;
	}
	
	public int noOfBullet(){
		return bullets.size();
	}

	//delete bullet if it is too long in our world 
	public boolean checkTTLBullet(RigidBody o, CollisionShape c){
		int i = bullets.indexOf(o);	//find index of current bullet
		ObjectBullet OBullet = info_bullets.get(i);
		if(System.nanoTime() > OBullet.getBulletTTL() +MAX_TTL_OF_BULLET*1000000000){
			dynamicsWorld.removeRigidBody(o);
			collisionShapes.remove(collisionShapes.indexOf(c));
			info_bullets.remove(i);
			bullets.remove(i);
			return true;
		}
		return false;
	}
	
	public String didHitModel(Transform t, RigidBody o, CollisionShape c){
		Transform obj = new Transform();
		Iterator iterator = SSG.playerList.keySet().iterator();
		while( iterator. hasNext() ){
			String playerID = (String)iterator.next();
			Player player = (Player) SSG.playerList.get(playerID);
			player.model.getWorldTransform(obj);
			if( ((obj.origin.x - COLLISION_FACTOR) < t.origin.x) && ((obj.origin.x +COLLISION_FACTOR) > t.origin.x) && ((obj.origin.y - COLLISION_FACTOR) < t.origin.y) && ((obj.origin.y +COLLISION_FACTOR) > t.origin.y) && ((obj.origin.z - COLLISION_FACTOR) < t.origin.z) && ((obj.origin.z +COLLISION_FACTOR) > t.origin.z)){
				//remove bullet
				int j = bullets.indexOf(o);	//find index of current bullet
				dynamicsWorld.removeRigidBody(o);
				collisionShapes.remove(collisionShapes.indexOf(c));
				info_bullets.remove(j);
				bullets.remove(j);
				return playerID;
			}
			
		}
		return "-1";
	}
}
